package com.yarolegovich.slidingrootnav;

import com.yarolegovich.slidingrootnav.callback.DragListener;
import com.yarolegovich.slidingrootnav.callback.DragStateListener;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.components.Component;
import ohos.agp.components.DragInfo;
import ohos.agp.components.StackLayout;
import ohos.app.Context;

import java.util.ArrayList;
import java.util.List;

public class SlidingRootNavLayout extends StackLayout implements SlidingRootNav {
    private boolean isMenuHidden;
    private boolean isMenuLocked;
    private boolean isContentClickableWhenMenuOpened;
    private int maxDragDistance;
    private List<DragListener> dragListeners;
    private List<DragStateListener> dragStateListeners;

    private float moveTotalX;
    private float layoutScale;
    private int slideTime;
    private int marginLeft;
    private SlideGravity slideDirection;
    private int offsetDistance;

    public SlidingRootNavLayout(Context context) {
        super(context);
        isMenuHidden = true;
        dragListeners = new ArrayList<>();
        dragStateListeners = new ArrayList<>();
        setListener();
    }

    @Override
    public boolean isMenuClosed() {
        return isMenuHidden;
    }

    @Override
    public boolean isMenuOpened() {
        return !isMenuHidden;
    }

    @Override
    public boolean isMenuLocked() {
        return isMenuLocked;
    }

    @Override
    public void closeMenu() {
        closeMenu(true);
    }

    @Override
    public void closeMenu(boolean animated) {
        if (!isMenuHidden) {
            changeMenuVisibility(animated, -maxDragDistance);
            isMenuHidden = true;
            for (DragStateListener listener : dragStateListeners) {
                listener.onDragStart();
            }
            for (DragStateListener listener : dragStateListeners) {
                listener.onDragEnd(isMenuOpened());
            }
        }
    }

    @Override
    public void openMenu() {
        openMenu(true);
    }

    @Override
    public void openMenu(boolean animated) {
        if (isMenuHidden) {
            changeMenuVisibility(animated, maxDragDistance);
            isMenuHidden = false;
            for (DragStateListener listener : dragStateListeners) {
                listener.onDragStart();
            }
            for (DragStateListener listener : dragStateListeners) {
                listener.onDragEnd(isMenuOpened());
            }
        }
        if (isContentClickableWhenMenuOpened) return;
    }

    @Override
    public void setMenuLocked(boolean locked) {
        isMenuLocked = locked;
    }

    @Override
    public SlidingRootNavLayout getLayout() {
        return this;
    }

    private void changeMenuVisibility(boolean animated, double xOffset) {
        if (isMenuLocked) {
            return;
        }
        double moveX;
        if (slideDirection == SlideGravity.LEFT) {
            if ((moveTotalX >= maxDragDistance && xOffset > 0) || (moveTotalX <= offsetDistance && xOffset < 0)) {
                return;
            }

            if (moveTotalX + xOffset >= maxDragDistance) {
                moveX = maxDragDistance - moveTotalX;
            } else if (moveTotalX + xOffset <= offsetDistance) {
                moveX = offsetDistance - moveTotalX;
            } else {
                moveX = xOffset;
            }
            moveTotalX += moveX;
        } else {
            if ((moveTotalX <= -maxDragDistance && xOffset < 0) || (moveTotalX >= offsetDistance && xOffset > 0)) {
                return;
            }

            if (moveTotalX + xOffset <= -maxDragDistance) {
                moveX = -maxDragDistance - moveTotalX;
            } else if (moveTotalX + xOffset >= offsetDistance) {
                moveX = offsetDistance - moveTotalX;
            } else {
                moveX = xOffset;
            }
            moveTotalX += moveX;
        }

        double scale = 1f - (1f - layoutScale) * Math.abs(moveTotalX / maxDragDistance);

        if ((moveTotalX == maxDragDistance || moveTotalX == offsetDistance) && animated) {
            AnimatorProperty animatorProperty = getComponentAt(1).createAnimatorProperty();
            animatorProperty.scaleX((float) scale).scaleY((float) scale);
            animatorProperty.moveByX((float) moveX);
            animatorProperty.setDuration((long) (slideTime * Math.abs(moveX) / maxDragDistance));
            animatorProperty.start();
        } else {
            LayoutConfig layoutConfig = (LayoutConfig) getComponentAt(1).getLayoutConfig();
            layoutConfig.setMarginLeft(marginLeft + (int) moveTotalX);
            layoutConfig.setMarginRight(-(int) moveTotalX);
            getComponentAt(1).setLayoutConfig(layoutConfig);
            getComponentAt(1).setScale((float) scale, (float) scale);
        }

        for (DragListener listener : dragListeners) {
            float dragProgress = Math.abs(moveTotalX / maxDragDistance);
            listener.onDrag(dragProgress);
        }
    }

    public void setContentClickableWhenMenuOpened(boolean contentClickableWhenMenuOpened) {
        isContentClickableWhenMenuOpened = contentClickableWhenMenuOpened;
    }

    public void setGravity(SlideGravity direction) {
        slideDirection = direction;
    }

    public void setOffsetDistance(int distance) {
        offsetDistance = distance;
    }

    public void setMaxDragDistance(int maxDragDistance) {
        this.maxDragDistance = maxDragDistance;
    }

    public void setLayoutScale(float layoutScale) {
        this.layoutScale = layoutScale;
    }

    public void addDragListener(DragListener listener) {
        dragListeners.add(listener);
    }

    public void addDragStateListener(DragStateListener listener) {
        dragStateListeners.add(listener);
    }

    public void removeDragListener(DragListener listener) {
        dragListeners.remove(listener);
    }

    public void removeDragStateListener(DragStateListener listener) {
        dragStateListeners.remove(listener);
    }

    private void setListener() {
        setDraggedListener(DRAG_HORIZONTAL, new DraggedListener() {
            @Override
            public void onDragDown(Component component, DragInfo dragInfo) {
                for (DragStateListener listener : dragStateListeners) {
                    listener.onDragStart();
                }
            }

            @Override
            public void onDragStart(Component component, DragInfo dragInfo) {
            }

            @Override
            public void onDragUpdate(Component component, DragInfo dragInfo) {
                changeMenuVisibility(true, dragInfo.xOffset);
            }

            @Override
            public void onDragEnd(Component component, DragInfo dragInfo) {
                judgeToSlideBottom();
            }

            @Override
            public void onDragCancel(Component component, DragInfo dragInfo) {
            }
        });
    }

    /**
     * 判断滑动到那边底部
     */
    private void judgeToSlideBottom() {
        int direation = slideDirection == SlideGravity.LEFT ? 1 : -1;
        if ((int) maxDragDistance / 2 > (int) moveTotalX * direation) {
            changeMenuVisibility(false, -maxDragDistance * direation);
            isMenuHidden = true;
        } else {
            changeMenuVisibility(false, maxDragDistance * direation);
            isMenuHidden = false;
        }

        for (DragStateListener listener : dragStateListeners) {
            listener.onDragEnd(isMenuOpened());
        }
    }

    public void setSlideTime(int time) {
        slideTime = time;
    }

    public void setLayoutConfig() {
        marginLeft = getComponentAt(1).getLayoutConfig().getMarginLeft();
    }
}
